using System;
using System.Collections.Generic;
using System.Text;

namespace CalcSig
{
    public class ImagePDistanceHash : ItemHash
    {
        public int PerceptualHash0To0;
        public int PerceptualHash0To90;
        public int PerceptualHash0To180;
        public int PerceptualHash0To270;
        public int PerceptualHash90To90;
        public int PerceptualHash90To180;
        public int PerceptualHash90To270;
        public int PerceptualHash180To180;
        public int PerceptualHash180To270;
        public int PerceptualHash270To270;
        public ImagePHash LeftPHash { get { return this.SourceData as ImagePHash; } }
        public ImagePHash TemporaryRightPHash;
    }

    class ImagePHashDistanceCalculator : HashCalculator<ImagePDistanceHash, ImagePHash>
    {
        private List<ImagePDistanceHash> collectedHashes = new List<ImagePDistanceHash>();

        public ImagePHashDistanceCalculator(IEnumerable<ImagePHash> recordEnumerator)
            : base(recordEnumerator)
        {
        }

        protected override void OnCalculateFileHash(ImagePDistanceHash itemHash)
        {
            if (itemHash.SourceData != null && itemHash.SourceData is ImagePHash)
            {
                List<ImagePDistanceHash> localCollectedHashes;
                lock (this.collectedHashes)
                {
                    localCollectedHashes = new List<ImagePDistanceHash>(this.collectedHashes); 
                    this.collectedHashes.Add(itemHash);
                }

                foreach (ImagePDistanceHash distanceHash in localCollectedHashes)
                {
                    itemHash.TemporaryRightPHash = distanceHash.LeftPHash;

                    itemHash.PerceptualHash0To0 = calculateDistance(itemHash.LeftPHash.PerceptualHash0, itemHash.TemporaryRightPHash.PerceptualHash0);
                    itemHash.PerceptualHash0To90 = calculateDistance(itemHash.LeftPHash.PerceptualHash0, itemHash.TemporaryRightPHash.PerceptualHash90);
                    itemHash.PerceptualHash0To180 = calculateDistance(itemHash.LeftPHash.PerceptualHash0, itemHash.TemporaryRightPHash.PerceptualHash180);
                    itemHash.PerceptualHash0To270 = calculateDistance(itemHash.LeftPHash.PerceptualHash0, itemHash.TemporaryRightPHash.PerceptualHash270);
                    itemHash.PerceptualHash90To90 = calculateDistance(itemHash.LeftPHash.PerceptualHash90, itemHash.TemporaryRightPHash.PerceptualHash90);
                    itemHash.PerceptualHash90To180 = calculateDistance(itemHash.LeftPHash.PerceptualHash90, itemHash.TemporaryRightPHash.PerceptualHash180);
                    itemHash.PerceptualHash90To270 = calculateDistance(itemHash.LeftPHash.PerceptualHash90, itemHash.TemporaryRightPHash.PerceptualHash270);
                    itemHash.PerceptualHash180To180 = calculateDistance(itemHash.LeftPHash.PerceptualHash180, itemHash.TemporaryRightPHash.PerceptualHash180);
                    itemHash.PerceptualHash180To270 = calculateDistance(itemHash.LeftPHash.PerceptualHash180, itemHash.TemporaryRightPHash.PerceptualHash270);
                    itemHash.PerceptualHash270To270 = calculateDistance(itemHash.LeftPHash.PerceptualHash270, itemHash.TemporaryRightPHash.PerceptualHash270);

                    base.OnCalculateFileHash(itemHash);
                }
            }
            else
            {
                throw new Exception("Internal Error (bug): in calculating hash, SourceData either null or is not ImagePHash");
            }
        }

        private int calculateDistance(PHash leftPHash, PHash rightPHash)
        {
            if (leftPHash.Length != rightPHash.Length)
            {
                throw new ArgumentOutOfRangeException("leftPHash.Length should equal rightPHash");
            }

            int distance = 0;

            for (int index = 0; index < leftPHash.Length; index++)
            {
                if (leftPHash[index] != rightPHash[index])
                {
                    distance++;
                }
            }

            return distance;
        }

        public override JobStats Estimate()
        {
            JobStats estimate = new JobStats();
            foreach (ImagePHash record in enumerableJob)
            {
                estimate.AddRecordToEstimates();
            }

            estimate.Records = estimate.Records * (estimate.Records - 1) / 2;

            return estimate;
        }
    }

    class ImagePHashDistanceFilterCalculator : IHashCalculator
    {
        protected readonly IHashCalculator _hashCalculator;
        protected readonly int _maxDistance;

        public ImagePHashDistanceFilterCalculator(IHashCalculator hashCalculator, int maxDistance)
        {
            _hashCalculator = hashCalculator;
            _maxDistance = maxDistance;

            _hashCalculator.ItemCalculated += delegate(ItemHash itemHash)
            {
                if (itemHash is ImagePDistanceHash)
                {
                    var distanceHash = itemHash as ImagePDistanceHash;
                    if (distanceHash.PerceptualHash0To0 <= _maxDistance
                        || distanceHash.PerceptualHash0To90 <= _maxDistance
                        || distanceHash.PerceptualHash0To180 <= _maxDistance
                        || distanceHash.PerceptualHash0To270 <= _maxDistance
                        || distanceHash.PerceptualHash90To90 <= _maxDistance
                        || distanceHash.PerceptualHash90To180 <= _maxDistance
                        || distanceHash.PerceptualHash90To270 <= _maxDistance
                        || distanceHash.PerceptualHash180To180 <= _maxDistance
                        || distanceHash.PerceptualHash180To270 <= _maxDistance
                        || distanceHash.PerceptualHash270To270 <= _maxDistance
                        )
                    {
                        if (ItemCalculated != null)
                        {
                            ItemCalculated(distanceHash);
                        }
                    }
                }
            };
        }

        public event ItemCalculatedDelegate ItemCalculated;

        public JobStats Estimate()
        {
            return _hashCalculator.Estimate();
        }

        public void CalculateHashes()
        {
            _hashCalculator.CalculateHashes();
        }
    }
}
